﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using Framework.Specifications;
using System.Linq.Expressions;
using Framework.Infrastructure;
using Framework.UnitOfWork;
using System.Reflection;

namespace Framework.Repositories
{
    public class DbCtxRepository<TEntity> : Repository<TEntity>
     where TEntity : class, IAggregateRoot, new()
    {
        public DbCtxRepository(IUnitOfWork unitOfWork)
            : base(unitOfWork)
        {

        }

        protected DbContext Container
        {
            get { return UnitOfWork.DbContext as DbContext; }
        }

        DbSet<TEntity> DbSet
        {
            get
            {
                return _objectSet ?? (_objectSet = Container.Set<TEntity>());
            }
        }
        DbSet<TEntity> _objectSet;

        protected override TEntity DoGet(ISpecification<TEntity> specification, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            return this.DoFind(specification, includePath);
        }

        protected override IQueryable<TEntity> DoGetAll(Expression<Func<TEntity, dynamic>> includePath = null)
        {
            return this.DoFindAll(includePath);
        }

        protected override IQueryable<TEntity> DoGetAll(ISpecification<TEntity> specification, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            return this.DoFindAll(specification, includePath);
        }

        protected override void DoAdd(TEntity entity)
        {
            DbSet.Add(entity);
        }

        protected override bool DoExists(ISpecification<TEntity> specification)
        {
            var query = DbSet.Where(specification.GetExpression());
            return query.Count() > 0;
        }

        protected override TEntity DoFind(ISpecification<TEntity> specification, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            if (includePath == null)
            {
                return DbSet.Where(specification.GetExpression()).FirstOrDefault();
            }
            else
            {
                return DbSet.Where(specification.GetExpression()).Include(includePath).FirstOrDefault();
            }

        }

        protected override IQueryable<TEntity> DoFindAll(Expression<Func<TEntity, dynamic>> includePath = null)
        {
            if (includePath == null)
            {
                return DbSet.AsQueryable();
            }
            else
            {
                return DbSet.Include(includePath).AsQueryable();
            }
        }

        protected override IQueryable<TEntity> DoFindAll(ISpecification<TEntity> specification, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            if (includePath == null)
            {
                return DbSet.Where(specification.GetExpression());
            }
            else
            {
                return DbSet.Where(specification.GetExpression()).Include(includePath);
            }
        }

        protected override TEntity DoGetByKey(params object[] keyValues)
        {
            return DbSet.Find(keyValues);
        }

        protected override void DoRemove(TEntity entity)
        {
            DbSet.Remove(entity);
        }

        protected override void DoUpdate(TEntity entity)
        {
            throw new NotImplementedException();
        }

        protected override IQueryable<TEntity> DoGetAll(ISpecification<TEntity> specification, Expression<Func<TEntity, dynamic>> sortPredicate, Storage.SortOrder sortOrder, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            return DoFindAll(specification, sortPredicate, sortOrder, includePath);
        }

        protected override IQueryable<TEntity> DoFindAll(ISpecification<TEntity> specification, Expression<Func<TEntity, dynamic>> sortPredicate, Storage.SortOrder sortOrder, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            IQueryable<TEntity> query = null;
            if (includePath == null)
            {
                query = DbSet.Where(specification.GetExpression());
            }
            else
            {
                query = DbSet.Where(specification.GetExpression()).Include(includePath);
            }

            return (sortOrder == Storage.SortOrder.Descending)
                                ?
                                    query
                                     .OrderByDescending(sortPredicate)
                                :
                                    query
                                     .OrderBy(sortPredicate);
        }

        protected override IQueryable<TEntity> DoPageFind(int pageIndex, int pageSize, Expression<Func<TEntity, dynamic>> orderByExpression, ISpecification<TEntity> specification, bool ascending, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            //checking arguments for this query 
            if (pageIndex < 0)
                throw new ArgumentException("InvalidPageIndex");

            if (pageSize <= 0)
                throw new ArgumentException("InvalidPageCount");

            if (orderByExpression == (Expression<Func<TEntity, dynamic>>)null)
                throw new ArgumentNullException("OrderByExpressionCannotBeNull");

            if (specification == (ISpecification<TEntity>)null)
            {
                specification = new AllSpecification<TEntity>();
            }

            //Create associated IObjectSet and perform query

            //this query cannot  use Paginate IQueryable extension method because Linq queries cannot be
            //merged with Object Builder methods. See Entity Framework documentation for more information
            IQueryable<TEntity> query = null;
            if (includePath == null)
            {
                query = DbSet.Where(specification.GetExpression());
            }
            else
            {
                query = DbSet.Where(specification.GetExpression()).Include(includePath);
            }
            return (ascending)
                                ?
                                    query
                                     .OrderBy(orderByExpression)
                                     .GetPageElements(pageIndex, pageSize)
                                :
                                    query
                                     .OrderByDescending(orderByExpression)
                                     .GetPageElements(pageIndex, pageSize);
        }

        protected override IQueryable<TEntity> DoPageFind(int pageIndex, int pageSize, Expression<Func<TEntity, dynamic>> orderByExpression, ISpecification<TEntity> specification, bool ascending, ref long totalCount, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            //checking arguments for this query 
            if (pageIndex < 0)
                throw new ArgumentException("InvalidPageIndex");

            if (pageSize <= 0)
                throw new ArgumentException("InvalidPageCount");

            if (orderByExpression == (Expression<Func<TEntity, dynamic>>)null)
                throw new ArgumentNullException("OrderByExpressionCannotBeNull");

            if (specification == (ISpecification<TEntity>)null)
            {
                specification = new AllSpecification<TEntity>();
            }

            //Create associated IObjectSet and perform query

            //this query cannot  use Paginate IQueryable extension method because Linq queries cannot be
            //merged with Object Builder methods. See Entity Framework documentation for more information
            IQueryable<TEntity> query = DbSet.Where(specification.GetExpression());
            totalCount = DbSet.Count(specification.GetExpression());


            if (includePath != null)
            {
                query = query.Include(includePath);
            }
            return (ascending)
                            ?
                                query
                                .OrderBy(orderByExpression)
                                .GetPageElements(pageIndex, pageSize)
                            :
                                query
                                .OrderByDescending(orderByExpression)
                                .GetPageElements(pageIndex, pageSize);
        }

        protected override IQueryable<TEntity> DoPageFind(int pageIndex, int pageSize, string[] orderByFileds, ISpecification<TEntity> specification, bool ascending, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            //checking arguments for this query 
            if (pageIndex < 0)
                throw new ArgumentException("InvalidPageIndex");

            if (pageSize <= 0)
                throw new ArgumentException("InvalidPageCount");

            var orderBys = new List<LambdaExpression>();
            if (orderByFileds != null && orderByFileds.Length > 0)
            {
                foreach (var field in orderByFileds)
                {
                    var le = Utility.GetLambdaExpression(typeof(TEntity), field);
                    orderBys.Add(le);
                }
            }
            if (specification == (ISpecification<TEntity>)null)
            {
                specification = new AllSpecification<TEntity>();
            }
            //Create associated IObjectSet and perform query

            //this query cannot  use Paginate IQueryable extension method because Linq queries cannot be
            //merged with Object Builder methods. See Entity Framework documentation for more information
            IQueryable<TEntity> query = null;
            if (includePath == null)
            {
                query = DbSet.Where(specification.GetExpression());
            }
            else
            {
                query = DbSet.Where(specification.GetExpression()).Include(includePath);
            }

            for (int i = 0; i < orderBys.Count; i ++)
            {
                var orderByExpression = orderBys[i];
                string orderByCMD = "OrderBy";
                if (i > 0)
                {
                    if (!ascending)
                    {
                        orderByCMD = "ThenByDescending";
                    }
                    else
                    {
                        orderByCMD = "ThenBy";
                    }
                }
                else
                {
                    if (!ascending)
                    {
                        orderByCMD = "OrderByDescending";
                    }
                }

                MethodCallExpression orderByCallExpression =
                        Expression.Call(typeof(Queryable),
                        orderByCMD,
                        new Type[] { typeof(TEntity),
                        orderByExpression.Body.Type},
                        query.Expression,
                        orderByExpression);
                query = query.Provider.CreateQuery<TEntity>(orderByCallExpression);
           
            }
            return query.GetPageElements(pageIndex, pageSize);
        }
     

        protected override IQueryable<TEntity> DoPageFind(int pageIndex, int pageSize, string[] orderByFileds, ISpecification<TEntity> specification, bool ascending, ref long totalCount, Expression<Func<TEntity, dynamic>> includePath = null)
        {
            //checking arguments for this query 
            if (pageIndex < 0)
                throw new ArgumentException("InvalidPageIndex");

            if (pageSize <= 0)
                throw new ArgumentException("InvalidPageCount");

            var orderBys = new List<LambdaExpression>();
            if (orderByFileds != null && orderByFileds.Length > 0)
            {
                foreach (var field in orderByFileds)
                {
                    var le = Utility.GetLambdaExpression(typeof(TEntity), field);
                    orderBys.Add(le);
                }
            }

            if (specification == (ISpecification<TEntity>)null)
            {
                specification = new AllSpecification<TEntity>();
            }

            //Create associated IObjectSet and perform query

            //this query cannot  use Paginate IQueryable extension method because Linq queries cannot be
            //merged with Object Builder methods. See Entity Framework documentation for more information
            IQueryable<TEntity> query = DbSet.Where(specification.GetExpression());
            totalCount = DbSet.Count(specification.GetExpression());

            if (includePath != null)
            {
                query = query.Include(includePath);
            }

            for (int i = 0; i < orderBys.Count; i++)
            {
                var orderByExpression = orderBys[i];
                string orderByCMD = "OrderBy";
                if (i > 0)
                {
                    if (!ascending)
                    {
                        orderByCMD = "ThenByDescending";
                    }
                    else
                    {
                        orderByCMD = "ThenBy";
                    }
                }
                else
                {
                    if (!ascending)
                    {
                        orderByCMD = "OrderByDescending";
                    }
                }
                MethodCallExpression orderByCallExpression =
                        Expression.Call(typeof(Queryable),
                        orderByCMD,
                        new Type[] { typeof(TEntity),
                        orderByExpression.Body.Type},
                        query.Expression,
                        orderByExpression);
                query = query.Provider.CreateQuery<TEntity>(orderByCallExpression);
            }
            return query.GetPageElements(pageIndex, pageSize);
        }

        protected override void DoAdd(IQueryable<TEntity> entities)
        {
            foreach (var entity in entities)
            {
                DoAdd(entity);
            }
        }
    }
}
